package com.duosecurity.duoweb;

import java.io.IOException;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;

public final class DuoWeb {
    private static final String DUO_PREFIX = "TX";
    private static final String APP_PREFIX = "APP";
    private static final String AUTH_PREFIX = "AUTH";

    private static final int DUO_EXPIRE = 300;
    private static final int APP_EXPIRE = 3600;

    private static final int IKEY_LEN = 20;
    private static final int SKEY_LEN = 40;
    private static final int AKEY_LEN = 40;

    public static final String ERR_USER = "ERR|The username passed to sign_request() is invalid.";
    public static final String ERR_IKEY = "ERR|The Duo integration key passed to sign_request() is invalid.";
    public static final String ERR_SKEY = "ERR|The Duo secret key passed to sign_request() is invalid.";
    public static final String ERR_AKEY = "ERR|The application secret key passed to sign_request() must be at least " + AKEY_LEN + " characters.";
    public static final String ERR_UNKNOWN = "ERR|An unknown error has occurred.";

    public static String signRequest(final String ikey, final String skey, final String akey, final String username) {
        return signRequest(ikey, skey, akey, username, System.currentTimeMillis() / 1000);
    }

    public static String signRequest(final String ikey, final String skey, final String akey, final String username, final long time) {
        final String duo_sig;
        final String app_sig;

        if (username.equals("")) {
            return ERR_USER;
        }
        if (username.indexOf('|') != -1) {
            return ERR_USER;
        }
        if (ikey.equals("") || ikey.length() != IKEY_LEN) {
            return ERR_IKEY;
        }
        if (skey.equals("") || skey.length() != SKEY_LEN) {
            return ERR_SKEY;
        }
        if (akey.equals("") || akey.length() < AKEY_LEN) {
            return ERR_AKEY;
        }

        try {
            duo_sig = signVals(skey, username, ikey, DUO_PREFIX, DUO_EXPIRE, time);
            app_sig = signVals(akey, username, ikey, APP_PREFIX, APP_EXPIRE, time);
        } catch (Exception e) {
            return ERR_UNKNOWN;
        }

        return duo_sig + ':' + app_sig;
    }

    public static String verifyResponse(final String ikey, final String skey, final String akey, final String sig_response)
        throws DuoWebException, NoSuchAlgorithmException, InvalidKeyException, IOException {
        return verifyResponse(ikey, skey, akey, sig_response, System.currentTimeMillis() / 1000);
    }

    public static String verifyResponse(final String ikey, final String skey, final String akey, final String sig_response, final long time)
        throws DuoWebException, NoSuchAlgorithmException, InvalidKeyException, IOException {
        String auth_user = null;
        String app_user = null;

        final String[] sigs = sig_response.split(":");
        final String auth_sig = sigs[0];
        final String app_sig = sigs[1];

        auth_user = parseVals(skey, auth_sig, AUTH_PREFIX, ikey, time);
        app_user = parseVals(akey, app_sig, APP_PREFIX, ikey, time);

        if (!auth_user.equals(app_user)) {
            throw new DuoWebException("Authentication failed.");
        }

        return auth_user;
    }

    private static String signVals(final String key, final String username, final String ikey, final String prefix, final int expire, final long time)
        throws InvalidKeyException, NoSuchAlgorithmException {
        final long expire_ts = time + expire;
        final String exp = Long.toString(expire_ts);

        final String val = username + '|' + ikey + '|' + exp;
        final String cookie = prefix + '|' + Base64.encodeBytes(val.getBytes());
        final String sig = Util.hmacSign(key, cookie);

        return cookie + '|' + sig;
    }

    private static String parseVals(final String key, final String val, final String prefix, final String ikey, final long time)
        throws InvalidKeyException, NoSuchAlgorithmException, IOException, DuoWebException {

        final String[] parts = val.split("\\|");
        if (parts.length != 3) {
            throw new DuoWebException("Invalid response");
        }

        final String u_prefix = parts[0];
        final String u_b64 = parts[1];
        final String u_sig = parts[2];

        final String sig = Util.hmacSign(key, u_prefix + '|' + u_b64);
        if (!Util.hmacSign(key, sig).equals(Util.hmacSign(key, u_sig))) {
            throw new DuoWebException("Invalid response");
        }

        if (!u_prefix.equals(prefix)) {
            throw new DuoWebException("Invalid response");
        }

        final byte[] decoded = Base64.decode(u_b64);
        final String cookie = new String(decoded);

        final String[] cookie_parts = cookie.split("\\|");
        if (cookie_parts.length != 3) {
            throw new DuoWebException("Invalid response");
        }
        final String username = cookie_parts[0];
        final String u_ikey = cookie_parts[1];
        final String expire = cookie_parts[2];

        if (!u_ikey.equals(ikey)) {
            throw new DuoWebException("Invalid response");
        }

        final long expire_ts = Long.parseLong(expire);
        if (time >= expire_ts) {
            throw new DuoWebException("Transaction has expired. Please check that the system time is correct.");
        }

        return username;
    }
}
